//    Copyright (C) Kherty.  All rights reserved.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Windows;
using System.Windows.Media;
using OpenLS.Core.Serialization;

namespace OpenLS.Drawing
{
    internal static class Enumerable
    {
        public static IEnumerable<TResult> Select<TSource, TResult>(IEnumerable<TSource> source,
                                                                    Core.Serialization.Func<TSource, TResult> selector)
        {
            Contract.Assert(source != null);
            Contract.Assert(selector != null);
            return selectIterator(source, selector);
        }

        private static IEnumerable<TResult> selectIterator<TSource, TResult>(IEnumerable<TSource> source,
                                                                             Core.Serialization.Func<TSource, TResult>
                                                                                 selector)
        {
            foreach (TSource sourceItem in source)
            {
                yield return selector(sourceItem);
            }
        }
    }

    public class PresetGeometry2D : Geometry
    {
        private ObservableCollection<GeometryGuide> _avLst; //\\move up?
        private ShapeType type = ShapeType.Rectangle;

        public ShapeType Type
        {
            get { return type; }
            set { type = value; }
        }

        public ObservableCollection<GeometryGuide> AdjustValueList
        {
            get
            {
                if (_avLst == null)
                    _avLst = new ObservableCollection<GeometryGuide>();
                return _avLst;
            }
            set { _avLst = value; }
        }

        internal override System.Windows.Media.Geometry GetWPFGeometry(ShapeProperties shape, bool arrows)
        {
            switch (Type)
            {
                case ShapeType.Rectangle:
                    return new RectangleGeometry(new Rect(new Size(100, 100)));
                case ShapeType.Star24:
                    string s24 = AdjustValueList[0].Formula.Replace("val ", "");

                    return GetStar(24, Math.Min(double.Parse(s24, CultureInfo.InvariantCulture) / 50000.0, 1.0));
                case ShapeType.Star32:
                    string s32 = AdjustValueList[0].Formula.Replace("val ", "");

                    return GetStar(32, Math.Min(double.Parse(s32, CultureInfo.InvariantCulture) / 50000.0, 1.0));
                case ShapeType.Ribbon2: //\\not implemented      
                case ShapeType.EllipseRibbon2:
                    return GetEllipseRibbon2();
                case ShapeType.WedgeRectCallout:
                    string val = AdjustValueList[0].Formula;
                    return WedgeRectCallout(val);
                case ShapeType.WedgeRoundRectCallout:
                    val = AdjustValueList[0].Formula;
                    return WedgeRoundRectCallout(val);
                case ShapeType.Ellipse:
                    if (shape == null || shape.Xfrm == null)
                    {
                        // Contract.Assert(false);
                        return new RectangleGeometry(new Rect(0, 0, 100, 100));
                    }
                    {
                        PositiveSize2D size = shape.Xfrm.Size;
                        return new EllipseGeometry(new Rect(new Size(size.Width, size.Height)));
                    }
                case ShapeType.Line:
                case ShapeType.StraightConnector1:
                    {
                        if (shape == null || shape.Xfrm == null)
                        {
                            // Contract.Assert(false);
                            return new RectangleGeometry(new Rect(0, 0, 100, 100));
                        }
                        PositiveSize2D size = shape.Xfrm.Size;
                        Point startPoint;
                        Point endPoint;
                        if (shape.Xfrm.FlipH)
                        {
                            if (shape.Xfrm.FlipV)
                            {
                                startPoint = new Point(size.Width, size.Height);
                                endPoint = new Point();
                            }
                            else
                            {
                                startPoint = new Point(size.Width, 0);
                                endPoint = new Point(0, size.Height);
                            }
                        }
                        else
                        {
                            if (shape.Xfrm.FlipV)
                            {
                                startPoint = new Point(0, size.Height);
                                endPoint = new Point(size.Width, 0);
                            }
                            else
                            {
                                startPoint = new Point(0, 0);
                                endPoint = new Point(size.Width, size.Height);
                            }
                        }
                        double angle = 0;
                        if (startPoint != endPoint)
                            angle = Math.Atan2(-(endPoint.Y - startPoint.Y), endPoint.X - startPoint.X);
                        PathFigure headGeometry = null;
                        PathFigure tailGeometry = null;
                        double arrowLength = 10;
                        if (shape.LineProperties != null)
                        {
                            LineProperties ln = shape.LineProperties;
                            if (ln.HeadEnd != null)
                            {
                                if (ln.HeadEnd.Type == LineEndType.Triangle)
                                {
                                    headGeometry = GetPathFigure(new[]
                                                                     {
                                                                         startPoint,
                                                                         MovePoint(startPoint, angle - Math.PI/8,
                                                                                   arrowLength),
                                                                         MovePoint(startPoint, angle + Math.PI/8,
                                                                                   arrowLength), startPoint
                                                                     }, false);
                                    startPoint = MovePoint(startPoint, angle, arrowLength);
                                }
                                if (ln.TailEnd.Type == LineEndType.Triangle)
                                {
                                    angle = angle + Math.PI;
                                    tailGeometry = GetPathFigure(new[]
                                                                     {
                                                                         endPoint,
                                                                         MovePoint(endPoint, +angle - Math.PI/8,
                                                                                   arrowLength)
                                                                         ,
                                                                         MovePoint(endPoint, +angle + Math.PI/8,
                                                                                   arrowLength)
                                                                         , endPoint
                                                                     }, false);
                                    endPoint = MovePoint(endPoint, angle, arrowLength);
                                }
                            }
                        }
                        if (!arrows)
                        {
                            return new PathGeometry(new List<PathFigure>
                                                        {
                                                            GetPathFigure(new[]
                                                                              {
                                                                                  startPoint,
                                                                                  endPoint
                                                                              }, true)
                                                        });
                        }

                        var figures = new List<PathFigure>();
                        figures.Add(GetPathFigure(new[]
                                                      {
                                                          startPoint,
                                                          endPoint
                                                      }, false));
                        if (headGeometry != null)
                            figures.Add(headGeometry);
                        if (tailGeometry != null)
                            figures.Add(tailGeometry);
                        return new PathGeometry(figures);
                    }
                default:
                    Debug.WriteLine("Unhandled " + Type);
                    return new RectangleGeometry(new Rect(new Size(100, 100)));
            }
        }

        private static PathFigure GetPathFigure(IEnumerable<Point> points, bool isStroke)
        {
            var l = new List<Point>(points);
            var pathSegments = new List<PathSegment>();
            foreach (LineSegment item in Enumerable.Select(points, p => new LineSegment(p, isStroke)))
            {
                pathSegments.Add(item);
            }
            return new PathFigure(l[0], pathSegments, false);
        }

        private static Point MovePoint(Point pt, double angle, double length)
        {
            var result = new Point(pt.X + length*Math.Cos(angle), pt.Y - length*Math.Sin(angle));
            return result;
        }

        internal override void ReadXmlCore(ReadContext context)
        {
            using (ReadContext c = context.Read("prstGeom"))
            {
                Type = c.GetEnum<ShapeType>("prst");
                List<GeometryGuide> l = c.ReadOptionalListElement<GeometryGuide>("avLst", "gd");
                if (l != null)
                {
                    _avLst = new ObservableCollection<GeometryGuide>();
                    foreach (GeometryGuide g in l)
                        _avLst.Add(g);
                }
                else
                    _avLst = null;
            }
        }

        internal override void WriteXmlCore(WriteContext context)
        {
            using (WriteContext c = context.Write("prstGeom"))
            {
                c.SetEnum("prst", Type);
                using (WriteContext c2 = context.Write("avLst"))
                {
                    foreach (GeometryGuide gd in AdjustValueList)
                        c2.WriteElement(gd);
                }
            }
        }

        private static System.Windows.Media.Geometry WedgeRoundRectCallout(string val)
        {
            return new RectangleGeometry(new Rect(0, 0, 100, 100), 5, 5); //\\
        }

        private static System.Windows.Media.Geometry WedgeRectCallout(string val)
        {
            return new RectangleGeometry(new Rect(0, 0, 100, 100), 0, 0); //\\
        }

        private static System.Windows.Media.Geometry GetEllipseRibbon2()
        {
            var g = new PathGeometry();

            var f = new PathFigure();
            f.StartPoint = new Point(0, 114);
            f.IsClosed = true;
            f.IsFilled = true;
            const bool b = true;
            f.Segments.Add(new BezierSegment(
                               new Point(27.5417322834646, 137.309501312336),
                               new Point(55.0834645669291, 132.857007874016),
                               new Point(82.6251968503937, 130.642729658793), b));
            f.Segments.Add(new BezierSegment(
                               new Point(72.3335433070866, 122.116430446194),
                               new Point(62.0418897637795, 113.590131233596),
                               new Point(51.7502362204724, 105.063832020997), b));
            f.Segments.Add(new BezierSegment(
                               new Point(99.5834120734908, 98.3130708661417),
                               new Point(147.416587926509, 98.3130708661417),
                               new Point(195.249763779528, 105.063832020997), b));
            f.Segments.Add(new BezierSegment(
                               new Point(184.95811023622, 113.590131233596),
                               new Point(174.666456692913, 122.116430446194),
                               new Point(164.374803149606, 130.642729658793), b));
            f.Segments.Add(new BezierSegment(
                               new Point(191.916535433071, 132.857007874016),
                               new Point(219.458267716535, 137.309501312336),
                               new Point(247, 144), b));
            f.Segments.Add(new BezierSegment(
                               new Point(236.708346456693, 125.1456167979),
                               new Point(226.416692913386, 106.291233595801),
                               new Point(216.125039370079, 87.4368503937008), b));
            f.Segments.Add(new BezierSegment(
                               new Point(226.416692913386, 72.9575853018373),
                               new Point(236.708346456693, 58.4784251968504),
                               new Point(247, 43.9991601049869), b));
            f.Segments.Add(new BezierSegment(
                               new Point(229.749921259843, 39.8088188976378),
                               new Point(212.499842519685, 36.4962729658793),
                               new Point(195.249763779528, 34.0617322834646), b));
            f.Segments.Add(new BezierSegment(
                               new Point(195.249763779528, 24.3954855643045),
                               new Point(195.249763779528, 14.729343832021),
                               new Point(195.249763779528, 5.06309711286089), b));
            f.Segments.Add(new BezierSegment(
                               new Point(147.416587926509, -1.68766404199475),
                               new Point(99.5834120734908, -1.68766404199475),
                               new Point(51.7502362204724, 5.06309711286089), b));
            f.Segments.Add(new BezierSegment(
                               new Point(51.7502362204724, 14.729343832021),
                               new Point(51.7502362204724, 24.3954855643045),
                               new Point(51.7502362204724, 34.0617322834646), b));
            f.Segments.Add(new BezierSegment(
                               new Point(34.500157480315, 36.4962729658793),
                               new Point(17.2500787401575, 39.8088188976378),
                               new Point(0, 43.9991601049869), b));
            f.Segments.Add(new BezierSegment(
                               new Point(10.2916535433071, 58.4784251968504),
                               new Point(20.5833070866142, 72.9575853018373),
                               new Point(30.8749606299213, 87.4368503937008), b));
            f.Segments.Add(new BezierSegment(
                               new Point(20.5833070866142, 106.291233595801),
                               new Point(10.2916535433071, 125.1456167979),
                               new Point(0, 144), b));
            g.Figures.Add(f);
            return g;
        }

        private static System.Windows.Media.Geometry GetStar(int p, double factor)
        {
            var g = new PathGeometry();
            var segments = new List<PathSegment>();
            for (int i = 0; i < p; i++)
            {
                double angle1 = i/(double) p*Math.PI*2;
                double angle2 = angle1 + 1/(double) p*Math.PI;
                PathSegment s = new LineSegment(new Point(
                                                    50 + 50*Math.Cos(angle1),
                                                    50 + 50*Math.Sin(angle1)), true);

                segments.Add(s);
                s = new LineSegment(new Point(
                                        50 + 50*factor*Math.Cos(angle2),
                                        50 + 50*factor*Math.Sin(angle2)), true);

                segments.Add(s);
            }
            var f = new PathFigure(new Point(100, 50), segments, true);
            g.Figures.Add(f);
            return g;
        }
    }
}